# -*- coding: utf-8 -*-
#
# $AIST_Release: 0.9.0 $
# Copyright 2011 Information Technology Research Institute, National
# Institute of Advanced Industrial Science and Technology
# 
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# 
#    http://www.apache.org/licenses/LICENSE-2.0
# 
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

"""
The APIs to handle file of Sawzall Clone Input/Output format.
"""

import sys
import subprocess
import struct
import os.path
from google.protobuf.internal import encoder
from google.protobuf.internal import decoder
from google.protobuf.internal import wire_format

from szlcln.filebuffer import FileBuffer 
from szlcln import table_pb2

class ProtocException(Exception): 
    """ 
    Exception when 'protoc' command fails.
    """
    pass

def protoc(protofile):
    """ 
    Compiles ProtocolBuffers DDL and imports compiled python file. 
    """
    try:
        d = os.path.dirname(protofile)
        retcode = subprocess.call("protoc --python_out=. -I %s %s" % (d, protofile), shell=True)
        if retcode != 0:
            raise ProtocException("protoc failed"), retcode

        f = os.path.basename(protofile).split('.', 2)[0]

        mod = __import__("%s_pb2" % f)
        return mod
    except OSError, e:
        print >>sys.stderr, "Can't execute protoc:", e

def _encodeFloat(fl):
    return struct.pack('<d', fl)

def _encodeStr(str):
    return str.encode('utf-8')

def _encodeBytes(str):
    return str

_TYPES = {
    int:     encoder._EncodeSignedVarint,
    float:   _encodeFloat,
    unicode: _encodeStr,
    str:     _encodeBytes
}

def _encode(val):
    if type(val) in _TYPES:
        return _TYPES[type(val)](val)
    else:
        return val.SerializeToString()


class TableWriter:
    """
    The key/value pairs writer.
    """

    def __init__(self, filename, appending = False):
        """
        Initializes object.
        This object opens the file specified by ``filename`` to write 
        key/value pairs.
        """

        mode = "w"
        if appending:
            mode = "a"
        self.fp = open(filename, mode)

    def write(self, key, value):
        """
        Writes key/value pair to the file.
        Key and value are encoded by the method corresponding to type.

        WARNING: This function encodes string as bytes.
        """

        table = table_pb2.Table()
        kv = table.data.add()
        kv.key   = _encode(key)
        kv.value = _encode(value)

        self.fp.write(table.SerializeToString())

    def close(self):
        """
        Closes the file.
        """
        self.fp.close()

def decodeInt(v):
    """ 
    Decodes byte string as integer.
    """
    i, _ = decoder._DecodeSignedVarint(v, 0)
    return i

def decodeFloat(bs):
    """ 
    Decodes byte string as floating point number.
    """
    return struct.unpack('<d', bs)[0]

def decodeString(bs):
    """ 
    Decodes byte string as string.
    """
    return unicode(bs, 'utf-8')

class InvalidFormatException(Exception): 
    """ 
    Exception when readTable() reads unkown tag.
    """
    pass

def readTable(filename):
    """ 
    Generator to read table. 
    This function yields a tuple of ``(key, value)`` for each key/value pair in the file.

    WARN: Key and value have not been decoded. you must decode key and value.
    """
    KEYVALUE_TAG = encoder.TagBytes(1, wire_format.WIRETYPE_LENGTH_DELIMITED)

    buf = FileBuffer(filename)
    pos = 0
    try:
        while len(buf) > pos:
            (tag, pos) = decoder.ReadTag(buf, pos)
            if tag != KEYVALUE_TAG:
                raise InvalidFormatException()
            pos = buf.drop(pos)

            (size, pos) = decoder._DecodeVarint(buf, pos)
            pos = buf.drop(pos)
            end = pos + size

            kv_bytes = buf[pos: end]
            kv = table_pb2.KeyValue()
            kv.ParseFromString(kv_bytes)

            yield (kv.key, kv.value)

            pos = buf.drop(end)

    finally:
        buf.close()
